

package com.hazelcast.client.config;

import com.hazelcast.client.LoadBalancer;
import com.hazelcast.client.util.RandomLB;
import com.hazelcast.client.util.RoundRobinLB;
import com.hazelcast.config.*;
import com.hazelcast.config.ConfigXmlGenerator.XmlGenerator;
import com.hazelcast.config.security.*;
import com.hazelcast.internal.cluster.Versions;
import com.hazelcast.internal.config.AliasedDiscoveryConfigUtils;
import com.hazelcast.internal.config.ConfigSections;
import com.hazelcast.internal.config.ConfigXmlGeneratorHelper;
import com.hazelcast.internal.util.Preconditions;
import com.hazelcast.nio.serialization.DataSerializableFactory;
import com.hazelcast.nio.serialization.PortableFactory;
import com.hazelcast.query.impl.IndexUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import static com.hazelcast.client.config.impl.ClientAliasedDiscoveryConfigUtils.aliasedDiscoveryConfigsFrom;
import static com.hazelcast.internal.util.StringUtil.isNullOrEmpty;
import static com.hazelcast.internal.util.XmlUtil.format;

/**
 * The ClientConfigXmlGenerator is responsible for transforming a
 * {@link ClientConfig} to a Hazelcast Client XML string.
 */
@SuppressWarnings("ClassFanOutComplexity")
public final class ClientConfigXmlGenerator {

    private ClientConfigXmlGenerator() {
    }

    /**
     * Convenience for {@link #generate(ClientConfig, int)}, which
     * generates the xml without any formatting.
     */
    public static String generate(ClientConfig clientConfig) {
        return generate(clientConfig, -1);
    }

    /**
     * Transforms the given {@link ClientConfig} to xml string
     * formatting the output with given {@code indent}, -1 means no
     * formatting.
     */
    public static String generate(ClientConfig clientConfig, int indent) {
        Preconditions.isNotNull(clientConfig, "ClientConfig");

        StringBuilder xml = new StringBuilder();
        xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        XmlGenerator gen = new XmlGenerator(xml);

        gen.open("hazelcast-client", "xmlns", "http://www.hazelcast.com/schema/client-config", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "http://www.hazelcast.com/schema/client-config " + "http://www.hazelcast.com/schema/client-config/hazelcast-client-config-" + Versions.CURRENT_CLUSTER_VERSION.getMajor() + '.' + Versions.CURRENT_CLUSTER_VERSION.getMinor() + ".xsd");

        //InstanceName
        gen.node("instance-name", clientConfig.getInstanceName());
        gen.node("cluster-name", clientConfig.getClusterName());
        //attributes
        gen.appendLabels(clientConfig.getLabels());
        //Properties
        gen.appendProperties(clientConfig.getProperties());
        //Network
        network(gen, clientConfig.getNetworkConfig());
        //Backup Ack To Client
        gen.node("backup-ack-to-client-enabled", clientConfig.isBackupAckToClientEnabled());
        //CP direct-to-leader operation routing
        gen.node("cp-direct-to-leader-routing", clientConfig.isCPDirectToLeaderRoutingEnabled());
        //Security
        security(gen, clientConfig.getSecurityConfig());
        //Listeners
        listener(gen, clientConfig.getListenerConfigs());
        //Serialization
        serialization(gen, clientConfig.getSerializationConfig());
        //Native
        nativeMemory(gen, clientConfig.getNativeMemoryConfig());
        //ProxyFactories
        proxyFactory(gen, clientConfig.getProxyFactoryConfigs());
        //LoadBalancer
        loadBalancer(gen, clientConfig.getLoadBalancer());
        //NearCache
        nearCaches(gen, clientConfig.getNearCacheConfigMap());
        //QueryCaches
        queryCaches(gen, clientConfig.getQueryCacheConfigs());
        //ConnectionStrategy
        connectionStrategy(gen, clientConfig.getConnectionStrategyConfig());
        //UserCodeDeployment
        userCodeDeployment(gen, clientConfig.getUserCodeDeploymentConfig());
        //FlakeIdGenerator
        flakeIdGenerator(gen, clientConfig.getFlakeIdGeneratorConfigMap());
        //Metrics
        metrics(gen, clientConfig.getMetricsConfig());
        instanceTrackingConfig(gen, clientConfig.getInstanceTrackingConfig());
        sql(gen, clientConfig.getSqlConfig());
        // TPC
        tpc(gen, clientConfig.getTpcConfig());
        //Diagnostics
        diagnostics(gen, clientConfig.getDiagnosticsConfig());

        //close HazelcastClient
        gen.close();

        return format(xml.toString(), indent);
    }

    private static void diagnostics(XmlGenerator gen, DiagnosticsConfig diagnosticsConfig) {
        gen.open(ConfigSections.DIAGNOSTICS.getName(), "enabled", diagnosticsConfig.isEnabled()).node("max-rolled-file-size-mb", diagnosticsConfig.getMaxRolledFileSizeInMB()).node("max-rolled-file-count", diagnosticsConfig.getMaxRolledFileCount()).node("include-epoch-time", diagnosticsConfig.isIncludeEpochTime()).node("log-directory", diagnosticsConfig.getLogDirectory()).node("file-name-prefix", diagnosticsConfig.getFileNamePrefix()).node("output-type", diagnosticsConfig.getOutputType());

        if (!diagnosticsConfig.getPluginProperties().isEmpty()) {
            gen.open("plugin-properties");

            for (Map.Entry<String, String> prop : diagnosticsConfig.getPluginProperties().entrySet()) {
                gen.nodeIfContents("property", prop.getValue(), "name", prop.getKey());
            }
            gen.close();
        }

        gen.close();
    }

    private static void network(XmlGenerator gen, ClientNetworkConfig network) {
        gen.open("network").node("cluster-routing", null, "mode", network.getClusterRoutingConfig().getRoutingMode().name()).node("redo-operation", network.isRedoOperation()).node("connection-timeout", network.getConnectionTimeout());

        clusterMembers(gen, network.getAddresses());
        socketOptions(gen, network.getSocketOptions());
        socketInterceptor(gen, network.getSocketInterceptorConfig());
        ssl(gen, network.getSSLConfig());
        cloud(gen, network.getCloudConfig());
        aliasedDiscoveryConfigsGenerator(gen, aliasedDiscoveryConfigsFrom(network));
        autoDetection(gen, network.getAutoDetectionConfig());
        discovery(gen, network.getDiscoveryConfig());
        outboundPort(gen, network.getOutboundPortDefinitions());
        icmp(gen, network.getClientIcmpPingConfig());

        gen.close();
    }

    private static void security(XmlGenerator gen, ClientSecurityConfig security) {
        if (security == null) {
            return;
        }
        gen.open("security");
        UsernamePasswordIdentityConfig upConfig = security.getUsernamePasswordIdentityConfig();
        if (upConfig != null) {
            gen.node("username-password", null, "username", upConfig.getUsername(), "password", upConfig.getPassword());
        }
        TokenIdentityConfig tic = security.getTokenIdentityConfig();
        if (tic != null) {
            gen.node("token", tic.getTokenEncoded(), "encoding", tic.getEncoding());
        }
        CredentialsFactoryConfig cfConfig = security.getCredentialsFactoryConfig();
        if (cfConfig != null) {
            gen.open("credentials-factory", "class-name", cfConfig.getClassName()).appendProperties(cfConfig.getProperties()).close();
        }
        kerberosIdentityGenerator(gen, security.getKerberosIdentityConfig());
        Map<String, RealmConfig> realms = security.getRealmConfigs();
        if (realms != null && !realms.isEmpty()) {
            gen.open("realms");
            for (Map.Entry<String, RealmConfig> realmEntry : realms.entrySet()) {
                securityRealmGenerator(gen, realmEntry.getKey(), realmEntry.getValue());
            }
            gen.close();
        }
        gen.close();
    }

    private static void kerberosIdentityGenerator(XmlGenerator gen, KerberosIdentityConfig c) {
        if (c == null) {
            return;
        }
        gen.open("kerberos").nodeIfContents("realm", c.getRealm()).nodeIfContents("principal", c.getPrincipal()).nodeIfContents("keytab-file", c.getKeytabFile()).nodeIfContents("security-realm", c.getSecurityRealm()).nodeIfContents("service-name-prefix", c.getServiceNamePrefix()).nodeIfContents("use-canonical-hostname", c.getUseCanonicalHostname()).nodeIfContents("spn", c.getSpn()).close();
    }

    private static void securityRealmGenerator(XmlGenerator gen, String name, RealmConfig c) {
        gen.open("realm", "name", name);
        if (c.isAuthenticationConfigured()) {
            gen.open("authentication");
            jaasAuthenticationGenerator(gen, c.getJaasAuthenticationConfig());
            gen.close();
        }
        gen.close();
    }

    private static void jaasAuthenticationGenerator(XmlGenerator gen, JaasAuthenticationConfig c) {
        if (c == null) {
            return;
        }
        appendLoginModules(gen, "jaas", c.getLoginModuleConfigs());
    }

    private static void appendLoginModules(XmlGenerator gen, String tag, List<LoginModuleConfig> loginModuleConfigs) {
        gen.open(tag);
        for (LoginModuleConfig lm : loginModuleConfigs) {
            List<String> attrs = new ArrayList<>();
            attrs.add("class-name");
            attrs.add(lm.getClassName());

            if (lm.getUsage() != null) {
                attrs.add("usage");
                attrs.add(lm.getUsage().name());
            }
            gen.open("login-module", attrs.toArray()).appendProperties(lm.getProperties()).close();
        }
        gen.close();
    }

    private static void listener(XmlGenerator gen, List<ListenerConfig> listeners) {
        if (listeners.isEmpty()) {
            return;
        }
        gen.open("listeners");
        for (ListenerConfig listener : listeners) {
            gen.node("listener", classNameOrImplClass(listener.getClassName(), listener.getImplementation()));
        }
        gen.close();
    }

    private static void serialization(XmlGenerator gen, SerializationConfig serialization) {
        gen.open("serialization").node("portable-version", serialization.getPortableVersion()).node("use-native-byte-order", serialization.isUseNativeByteOrder()).node("byte-order", serialization.getByteOrder()).node("enable-compression", serialization.isEnableCompression()).node("enable-shared-object", serialization.isEnableSharedObject()).node("allow-unsafe", serialization.isAllowUnsafe()).node("allow-override-default-serializers", serialization.isAllowOverrideDefaultSerializers()).node("check-class-def-errors", serialization.isCheckClassDefErrors());


        Map<Integer, String> dsfClasses = serialization.getDataSerializableFactoryClasses();
        Map<Integer, DataSerializableFactory> dsfImpls = serialization.getDataSerializableFactories();
        if (!dsfClasses.isEmpty() || !dsfImpls.isEmpty()) {
            gen.open("data-serializable-factories");
            for (Map.Entry<Integer, String> entry : dsfClasses.entrySet()) {
                gen.node("data-serializable-factory", entry.getValue(), "factory-id", entry.getKey());
            }
            for (Map.Entry<Integer, DataSerializableFactory> entry : dsfImpls.entrySet()) {
                gen.node("data-serializable-factory", entry.getValue().getClass().getName(), "factory-id", entry.getKey());
            }
            gen.close();
        }

        Map<Integer, String> portableClasses = serialization.getPortableFactoryClasses();
        Map<Integer, PortableFactory> portableImpls = serialization.getPortableFactories();
        if (!portableClasses.isEmpty() || !portableImpls.isEmpty()) {
            gen.open("portable-factories");
            for (Map.Entry<Integer, String> entry : portableClasses.entrySet()) {
                gen.node("portable-factory", entry.getValue(), "factory-id", entry.getKey());
            }
            for (Map.Entry<Integer, PortableFactory> entry : portableImpls.entrySet()) {
                gen.node("portable-factory", entry.getValue().getClass().getName(), "factory-id", entry.getKey());
            }
            gen.close();
        }

        serializers(gen, serialization);

        ConfigXmlGeneratorHelper.compactSerialization(gen, serialization.getCompactSerializationConfig());

        //close serialization
        gen.close();
    }

    private static void serializers(XmlGenerator gen, SerializationConfig serialization) {
        GlobalSerializerConfig global = serialization.getGlobalSerializerConfig();
        Collection<SerializerConfig> serializers = serialization.getSerializerConfigs();
        if (global != null || !serializers.isEmpty()) {
            gen.open("serializers");
            if (global != null) {
                gen.node("global-serializer", classNameOrImplClass(global.getClassName(), global.getImplementation()), "override-java-serialization", global.isOverrideJavaSerialization());
            }
            for (SerializerConfig serializer : serializers) {
                gen.node("serializer", null, "type-class", classNameOrClass(serializer.getTypeClassName(), serializer.getTypeClass()), "class-name", classNameOrImplClass(serializer.getClassName(), serializer.getImplementation()));
            }
            //close serializers
            gen.close();
        }
    }

    private static void nativeMemory(XmlGenerator gen, NativeMemoryConfig nativeMemory) {
        gen.open("native-memory", "enabled", nativeMemory.isEnabled(), "allocator-type", nativeMemory.getAllocatorType()).node("capacity", null, "value", nativeMemory.getCapacity().getValue(), "unit", nativeMemory.getCapacity().getUnit()).node("min-block-size", nativeMemory.getMinBlockSize()).node("page-size", nativeMemory.getPageSize()).node("metadata-space-percentage", nativeMemory.getMetadataSpacePercentage());

        PersistentMemoryConfig pmemConfig = nativeMemory.getPersistentMemoryConfig();
        List<PersistentMemoryDirectoryConfig> directoryConfigs = pmemConfig.getDirectoryConfigs();
        gen.open("persistent-memory", "enabled", pmemConfig.isEnabled(), "mode", pmemConfig.getMode().name());
        if (!directoryConfigs.isEmpty()) {
            gen.open("directories");
            for (PersistentMemoryDirectoryConfig dirConfig : directoryConfigs) {
                if (dirConfig.isNumaNodeSet()) {
                    gen.node("directory", dirConfig.getDirectory(), "numa-node", dirConfig.getNumaNode());
                } else {
                    gen.node("directory", dirConfig.getDirectory());
                }
            }
            gen.close();
        }
        gen.close().close();
    }

    private static void proxyFactory(XmlGenerator gen, List<ProxyFactoryConfig> proxyFactories) {
        if (proxyFactories.isEmpty()) {
            return;
        }
        gen.open("proxy-factories");
        for (ProxyFactoryConfig proxyFactory : proxyFactories) {
            gen.node("proxy-factory", null, "class-name", classNameOrImplClass(proxyFactory.getClassName(), proxyFactory.getFactoryImpl()), "service", proxyFactory.getService());
        }
        gen.close();
    }

    private static void loadBalancer(XmlGenerator gen, LoadBalancer loadBalancer) {
        if (loadBalancer == null) {
            return;
        }
        String type;
        if (loadBalancer instanceof RandomLB) {
            type = "random";
        } else
            if (loadBalancer instanceof RoundRobinLB) {
                type = "round-robin";
            } else {
                type = "custom";
            }

        if ("custom".equals(type)) {
            gen.node("load-balancer", loadBalancer.getClass().getName(), "type", type);
        } else {
            gen.node("load-balancer", null, "type", type);
        }
    }

    private static void nearCaches(XmlGenerator gen, Map<String, NearCacheConfig> nearCacheMap) {
        if (nearCacheMap.isEmpty()) {
            return;
        }
        for (Map.Entry<String, NearCacheConfig> entry : nearCacheMap.entrySet()) {
            nearCache(gen, entry.getKey(), entry.getValue());
        }
    }

    private static void queryCaches(XmlGenerator gen, Map<String, Map<String, QueryCacheConfig>> queryCaches) {
        if (queryCaches.isEmpty()) {
            return;
        }
        gen.open("query-caches");
        for (Map.Entry<String, Map<String, QueryCacheConfig>> entry : queryCaches.entrySet()) {
            String mapName = entry.getKey();
            Map<String, QueryCacheConfig> queryCachesPerMap = entry.getValue();
            for (QueryCacheConfig queryCache : queryCachesPerMap.values()) {
                EvictionConfig evictionConfig = queryCache.getEvictionConfig();
                gen.open("query-cache", "mapName", mapName, "name", queryCache.getName()).node("include-value", queryCache.isIncludeValue()).node("in-memory-format", queryCache.getInMemoryFormat()).node("populate", queryCache.isPopulate()).node("coalesce", queryCache.isCoalesce()).node("delay-seconds", queryCache.getDelaySeconds()).node("batch-size", queryCache.getBatchSize()).node("serialize-keys", queryCache.isSerializeKeys()).node("buffer-size", queryCache.getBufferSize()).node("eviction", null, "size", evictionConfig.getSize(), "max-size-policy", evictionConfig.getMaxSizePolicy(), "eviction-policy", evictionConfig.getEvictionPolicy(), "comparator-class-name", classNameOrImplClass(evictionConfig.getComparatorClassName(), evictionConfig.getComparator()));
                queryCachePredicate(gen, queryCache.getPredicateConfig());
                entryListeners(gen, queryCache.getEntryListenerConfigs());
                IndexUtils.generateXml(gen, queryCache.getIndexConfigs(), false);
                //close query-cache
                gen.close();
            }
        }
        //close query-caches
        gen.close();
    }

    private static void userCodeDeployment(XmlGenerator gen, ClientUserCodeDeploymentConfig userCodeDeployment) {
        gen.open("user-code-deployment", "enabled", userCodeDeployment.isEnabled());
        List<String> classNames = userCodeDeployment.getClassNames();
        if (!classNames.isEmpty()) {
            gen.open("classNames");
            for (String className : classNames) {
                gen.node("className", className);
            }
            //close classNames
            gen.close();
        }
        List<String> jarPaths = userCodeDeployment.getJarPaths();
        if (!jarPaths.isEmpty()) {
            gen.open("jarPaths");
            for (String jarPath : jarPaths) {
                gen.node("jarPath", jarPath);
            }
            //close jarPaths
            gen.close();
        }

        //close user-code-deployment
        gen.close();
    }

    private static void flakeIdGenerator(XmlGenerator gen, Map<String, ClientFlakeIdGeneratorConfig> flakeIdGenerators) {
        for (Map.Entry<String, ClientFlakeIdGeneratorConfig> entry : flakeIdGenerators.entrySet()) {
            ClientFlakeIdGeneratorConfig flakeIdGenerator = entry.getValue();
            gen.open("flake-id-generator", "name", entry.getKey()).node("prefetch-count", flakeIdGenerator.getPrefetchCount()).node("prefetch-validity-millis", flakeIdGenerator.getPrefetchValidityMillis()).close();
        }
    }

    private static void entryListeners(XmlGenerator gen, List<EntryListenerConfig> entryListeners) {
        if (entryListeners.isEmpty()) {
            return;
        }
        gen.open("entry-listeners");
        for (EntryListenerConfig listener : entryListeners) {
            gen.node("entry-listener", classNameOrImplClass(listener.getClassName(), listener.getImplementation()), "include-value", listener.isIncludeValue(), "local", listener.isLocal());
        }
        gen.close();
    }

    private static void queryCachePredicate(XmlGenerator gen, PredicateConfig predicate) {
        String sql = predicate.getSql();
        String content = sql != null ? sql : classNameOrImplClass(predicate.getClassName(), predicate.getImplementation());
        String type = sql != null ? "sql" : "class-name";
        gen.node("predicate", content, "type", type);
    }

    private static void clusterMembers(XmlGenerator gen, List<String> addresses) {
        if (addresses == null || addresses.isEmpty()) {
            return;
        }
        gen.open("cluster-members");
        for (String address : addresses) {
            gen.node("address", address);
        }
        gen.close();
    }

    private static void socketOptions(XmlGenerator gen, SocketOptions socketOptions) {
        gen.open("socket-options").node("tcp-no-delay", socketOptions.isTcpNoDelay()).node("keep-alive", socketOptions.isKeepAlive()).node("reuse-address", socketOptions.isReuseAddress()).node("linger-seconds", socketOptions.getLingerSeconds()).node("buffer-size", socketOptions.getBufferSize()).close();
    }

    private static void socketInterceptor(XmlGenerator gen, SocketInterceptorConfig socketInterceptor) {
        if (socketInterceptor == null) {
            return;
        }
        gen.open("socket-interceptor", "enabled", socketInterceptor.isEnabled()).node("class-name", classNameOrImplClass(socketInterceptor.getClassName(), socketInterceptor.getImplementation())).appendProperties(socketInterceptor.getProperties()).close();
    }

    private static void cloud(XmlGenerator gen, ClientCloudConfig cloudConfig) {
        gen.open("hazelcast-cloud", "enabled", cloudConfig.isEnabled()).node("discovery-token", cloudConfig.getDiscoveryToken()).close();
    }

    private static void ssl(XmlGenerator gen, SSLConfig ssl) {
        if (ssl == null) {
            return;
        }
        gen.open("ssl", "enabled", ssl.isEnabled()).node("factory-class-name", classNameOrImplClass(ssl.getFactoryClassName(), ssl.getFactoryImplementation())).appendProperties(ssl.getProperties()).close();
    }

    private static void aliasedDiscoveryConfigsGenerator(XmlGenerator gen, List<AliasedDiscoveryConfig<?>> configs) {
        if (configs == null) {
            return;
        }
        for (AliasedDiscoveryConfig<?> c : configs) {
            gen.open(AliasedDiscoveryConfigUtils.tagFor(c), "enabled", c.isEnabled());
            if (c.isUsePublicIp()) {
                gen.node("use-public-ip", "true");
            }
            for (String key : c.getProperties().keySet()) {
                gen.node(key, c.getProperties().get(key));
            }
            gen.close();
        }
    }

    private static void autoDetection(XmlGenerator gen, AutoDetectionConfig config) {
        if (config == null) {
            return;
        }
        gen.open("auto-detection", "enabled", config.isEnabled()).close();
    }

    private static void discovery(XmlGenerator gen, DiscoveryConfig discovery) {
        if (discovery.getNodeFilter() == null && discovery.getNodeFilterClass() == null && discovery.getDiscoveryStrategyConfigs().isEmpty()) {
            return;
        }
        gen.open("discovery-strategies").node("node-filter", null, "class", classNameOrImplClass(discovery.getNodeFilterClass(), discovery.getNodeFilter()));
        for (DiscoveryStrategyConfig strategy : discovery.getDiscoveryStrategyConfigs()) {
            gen.open("discovery-strategy", "class", strategy.getClassName(), "enabled", true).appendProperties(strategy.getProperties(), "properties").close();
        }
        gen.close();
    }

    private static void outboundPort(XmlGenerator gen, Collection<String> outboundPortDefinitions) {
        if (outboundPortDefinitions != null && !outboundPortDefinitions.isEmpty()) {
            gen.open("outbound-ports");
            for (String portDefinitions : outboundPortDefinitions) {
                gen.node("ports", portDefinitions);
            }
            gen.close();
        }
    }

    private static void icmp(XmlGenerator gen, ClientIcmpPingConfig icmp) {
        gen.open("icmp-ping", "enabled", icmp.isEnabled()).node("timeout-milliseconds", icmp.getTimeoutMilliseconds()).node("interval-milliseconds", icmp.getIntervalMilliseconds()).node("ttl", icmp.getTtl()).node("max-attempts", icmp.getMaxAttempts()).node("echo-fail-fast-on-startup", icmp.isEchoFailFastOnStartup()).close();
    }

    private static void nearCache(XmlGenerator gen, String name, NearCacheConfig nearCache) {
        EvictionConfig eviction = nearCache.getEvictionConfig();
        NearCachePreloaderConfig preloader = nearCache.getPreloaderConfig();
        gen.open("near-cache", "name", name).node("in-memory-format", nearCache.getInMemoryFormat()).node("serialize-keys", nearCache.isSerializeKeys()).node("invalidate-on-change", nearCache.isInvalidateOnChange()).node("time-to-live-seconds", nearCache.getTimeToLiveSeconds()).node("max-idle-seconds", nearCache.getMaxIdleSeconds()).node("local-update-policy", nearCache.getLocalUpdatePolicy()).node("eviction", null, "size", eviction.getSize(), "max-size-policy", eviction.getMaxSizePolicy(), "eviction-policy", eviction.getEvictionPolicy(), "comparator-class-name", classNameOrImplClass(eviction.getComparatorClassName(), eviction.getComparator())).node("preloader", null, "enabled", preloader.isEnabled(), "directory", preloader.getDirectory(), "store-initial-delay-seconds", preloader.getStoreInitialDelaySeconds(), "store-interval-seconds", preloader.getStoreIntervalSeconds());
        //close near-cache
        gen.close();
    }

    private static void connectionStrategy(XmlGenerator gen, ClientConnectionStrategyConfig connectionStrategy) {
        ConnectionRetryConfig connectionRetry = connectionStrategy.getConnectionRetryConfig();
        gen.open("connection-strategy", "async-start", connectionStrategy.isAsyncStart(), "reconnect-mode", connectionStrategy.getReconnectMode());
        gen.open("connection-retry").node("initial-backoff-millis", connectionRetry.getInitialBackoffMillis()).node("max-backoff-millis", connectionRetry.getMaxBackoffMillis()).node("multiplier", connectionRetry.getMultiplier()).node("cluster-connect-timeout-millis", connectionRetry.getClusterConnectTimeoutMillis()).node("jitter", connectionRetry.getJitter()).close();

        // close connection-strategy
        gen.close();
    }

    private static String classNameOrClass(String className, Class clazz) {
        return !isNullOrEmpty(className) ? className : clazz != null ? clazz.getName() : null;
    }

    private static String classNameOrImplClass(String className, Object impl) {
        return !isNullOrEmpty(className) ? className : impl != null ? impl.getClass().getName() : null;
    }

    private static void metrics(XmlGenerator gen, ClientMetricsConfig metricsConfig) {
        gen.open("metrics", "enabled", metricsConfig.isEnabled()).open("jmx", "enabled", metricsConfig.getJmxConfig().isEnabled()).close().node("collection-frequency-seconds", metricsConfig.getCollectionFrequencySeconds()).close();
    }

    private static void instanceTrackingConfig(XmlGenerator gen, InstanceTrackingConfig trackingConfig) {
        gen.open("instance-tracking", "enabled", trackingConfig.isEnabled()).node("file-name", trackingConfig.getFileName()).node("format-pattern", trackingConfig.getFormatPattern()).close();
    }

    private static void sql(XmlGenerator gen, ClientSqlConfig sqlConfig) {
        gen.open("sql").node("resubmission-mode", sqlConfig.getResubmissionMode().name()).close();
    }

    private static void tpc(XmlGenerator gen, ClientTpcConfig tpcConfig) {
        gen.open("tpc", "enabled", tpcConfig.isEnabled()).node("connection-count", tpcConfig.getConnectionCount()).close();
    }
}
